/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.iftk.xmlparser.service;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.iftk.xmlparser.domain.*;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.XmlReader;
import java.io.*;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

/**
 *
 * @author treiman
 */
public class ContentParserServiceImpl {
    
    // final values for the URLs used to fetch information
    private final String CS_STUDIES_URL = "http://www.cs.helsinki.fi/opiskelu/";
    private final String KATA_URL = "https://ilmo.cs.helsinki.fi/kata/servlet/JsonTuloste?periodi=Periodi";
    private final String MENU_EXACTUM = "http://www.unicafe.fi/rss/eng/11/";
    private final String MENU_CHEMICUM = "http://www.unicafe.fi/rss/eng/10/";
    private final String NEWS_FEED = "http://www.cs.helsinki.fi/en/news/92/feed";
    private final String EVENT_FEED = "http://www.cs.helsinki.fi/en/tapahtuma/179/feed";
    private final String PERSONNEL_URL = "http://www.cs.helsinki.fi/en/people";
    private final String RESEARCH_GROUPS_URL = "http://www.cs.helsinki.fi/en/research";
    private final String PRICE_LIST_URL = "http://www.unicafe.fi/lounas/hinnasto";
    
    private List<Period> periodList;
    private CourseList courseList;
    private News newsList;
    private Events eventList;
    private Menus menuList;
    private Personnel personList;
    private ResearchGroups researchGroups;
    private Maps maps;
    private PriceList priceList;
    
    // default constructor
    public ContentParserServiceImpl() {
        courseList = new CourseList();
        newsList = new News();
        eventList = new Events();
        menuList = new Menus();
        personList = new Personnel();
        researchGroups = new ResearchGroups();
        maps = new Maps();
    }

    // accessors
    public String getCS_STUDIES_URL() {
        return CS_STUDIES_URL;
    }

    public String getEVENT_FEED() {
        return EVENT_FEED;
    }

    public String getKATA_URL() {
        return KATA_URL;
    }

    public String getMENU_CHEMICUM() {
        return MENU_CHEMICUM;
    }

    public String getMENU_EXACTUM() {
        return MENU_EXACTUM;
    }

    public String getNEWS_FEED() {
        return NEWS_FEED;
    }

    public CourseList getCourseList() {
        return courseList;
    }

    public Events getEventList() {
        return eventList;
    }

    public Menus getMenuList() {
        return menuList;
    }

    public News getNewsList() {
        return newsList;
    }

    public List<Period> getPeriodList() {
        return periodList;
    }
    
    // method for reader the contents from and URL as an input stream
    // ** DEPRECATED **
    private String readUrl(String urlString) throws Exception {
        BufferedReader reader = null;
        try {
            URL url = new URL(urlString);
            reader = new BufferedReader(new InputStreamReader(url.openStream()));
            StringBuilder buffer = new StringBuilder();
            int read;
            char[] chars = new char[1024];
            while ((read = reader.read(chars)) != -1)
                buffer.append(chars, 0, read); 

            return buffer.toString();
        }
        finally {
            if (reader != null) {
                reader.close();
            }
        }
    }
    
    // method to build the complete URL used to fetch course information
    private String buildURL() {
        String url = KATA_URL;
        Calendar cal = Calendar.getInstance();
        // get the current year
        int year = cal.get(Calendar.YEAR);
        
        StringBuilder result = new StringBuilder();
        result.append(url);
        result.append("+");
        // append the period number to the post query
        int period = getCurrentPeriod();
        result.append(period);
        result.append("+");
        result.append(year);
        
        return result.toString();
    }
    
    // helper method for opening and input stream from an URL
    private BufferedReader readWebSite(String url) throws Exception {
        return new BufferedReader(new InputStreamReader(new URL(url).openStream()));
    }
    
    // method for building the period information based on the department web site
    private void buildPeriods() throws Exception {
        BufferedReader reader = readWebSite(CS_STUDIES_URL);
        String line = reader.readLine();
        String semester;
        int startYear = 0;
        int endYear = 0;
        ArrayList<Integer> tokens = new ArrayList();
        List<Period> periods = new ArrayList();
        boolean flag = false;
        int periodsFound = 0;
        
        // search for <td> elements containing period numbers (I to IV)
        // NOTE: can be reimplemented to use more sophisticated parsing methods
        while(line != null) {
            if(line.equals("<td>I</td>") || line.equals("<td>II</td>") ||
                    line.equals("<td>III</td>") || line.equals("<td>IV</td>")) {
                periodsFound++;
                // set a flag if matches were found
                flag = true;
            }
            // look for an element containing the string "Lukuvuosi", meaning "Semester"
            else if(line.contains("Lukuvuosi")) {
                semester = line;
                // pattern for recognizing a four-digit integer (year)
                Matcher m = Pattern.compile("\\d\\d\\d\\d").matcher(semester);

                int counter = 0;
                while(m.find()) {
                    // first of the result is the starting year of the semester
                    if(counter == 0) {
                        startYear = Integer.parseInt(m.group());
                    }
                    // and the second one is the ending year of the semester
                    // as in Semester x: dddd - dddd
                    else if(counter == 1) {
                        endYear = Integer.parseInt(m.group());
                    }
                    ++counter;            
                }                
            }
            line = reader.readLine();
            // if there was a match, parse the two-digit numbers:
            // they will be the day of month and the month
            if(flag) {
                Matcher m = Pattern.compile("\\d\\d").matcher(line);
                int counter = 0;
                // create a new period
                Period p = new Period();
                // clear the periods default calendar object
                p.getBegin().clear();
                p.getEnd().clear();
                // add the period to the list
                periods.add(p);
                while(m.find()) {
                    ++counter;
                    tokens.add(counter-1, Integer.parseInt(m.group()));
                }
                // build the period's calendar object based on the period number
                // period number is based on periodsFound ([1, 4])
                switch(periodsFound) {
                    case 1: p.getBegin().set(startYear, tokens.get(1)-1, tokens.get(0));
                            p.getEnd().set(startYear, tokens.get(3)-1, tokens.get(2));
                            break;
                    case 2: p.getBegin().set(startYear, tokens.get(1)-1, tokens.get(0));
                            p.getEnd().set(startYear, tokens.get(3)-1, tokens.get(2));
                            break;
                    case 3: p.getBegin().set(endYear, tokens.get(1)-1, tokens.get(0));
                            p.getEnd().set(endYear, tokens.get(3)-1, tokens.get(2));
                            break;
                    case 4: p.getBegin().set(endYear, tokens.get(1)-1, tokens.get(0));
                            p.getEnd().set(endYear, tokens.get(3)-1, tokens.get(2));
                            break;
                    default:    break;
                }
                // set the period number
                p.setPeriodNumber(periodsFound);
            }
            // reset the flag
            flag = false;
        }
        
        // close the reader
        if(reader != null) {
            reader.close();
        }     
        
        // set periodList to point to the newly created list of periods
        periodList = periods;
    }
    
    // method for finding out the ongoing period number
    private int getCurrentPeriod() {
        
        try {
            // build the periods as shown above
            buildPeriods();
        }
        catch(Exception e) {
            System.err.println("Error!");
        }

        // get the date
        Calendar cal = Calendar.getInstance();
        
        // iterate through the periods built above
        Period prev = null;
        
        for(Period p : periodList) {

            // return the period that has started but has not ended
            if(cal.after(p.getBegin()) && cal.before(p.getEnd())) {
                return p.getPeriodNumber();
            }
            
            else if(prev != null) {
                if(cal.after(prev.getEnd()) && cal.before(p.getBegin())) {
                    return p.getPeriodNumber();
                }
            }            
            
            prev = p;
        }
        
        // if no period was ongoing, return period 5
        return 5;
        
    }
    
    // method for parsing the courses from an JSON object
    public void runJSONParser() {
        // create an empty list of courses
        List<Course> courses = null;
        // create a new instance of the Gson class
        Gson gson = new Gson();
        
        try {
            // stream the data from KATA as ISO-8859-1
            URL url = new URL(buildURL());
            BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "ISO-8859-1"));
            courses = gson.fromJson(reader, new TypeToken<List<Course>>(){}.getType());
        }
        catch(Exception e) {
            
        }
        // set the course list to point to the one we created
        courseList.setCourses(courses);
        // sort the courses based on the study level of the course
        courseList.sort();
    }
    
    // method for build the event list from an RSS feed
    private void buildEvents() throws Exception {
        
        // get the feed from the URL
        SyndFeed feed = getRSSFeed(EVENT_FEED);
        DepartmentEvent event;
        
        int i = 0;
        
        // iterate through the RSS feed's entries
        for(Iterator it = feed.getEntries().iterator(); it.hasNext();) {
            SyndEntry entry = (SyndEntry)it.next();
            // only get the next 15 upcoming events to save interface space
            if(i < 15) {
                // only increment the counter [0,14] if the description was not empty
                if(entry != null && entry.getDescription() != null) {
                    // create a new event object
                    event = new DepartmentEvent();
                    // set the title based on the entry's <title>
                    event.setTitle(entry.getTitle().trim());
                    // set the contents based on the entry's <description>
                    event.setContents(entry.getDescription().getValue().trim());
                    // add the event to the list
                    eventList.add(event);
                    // increment the counter
                    i++;
                }                
            }
        }
    }
    
    // method for building the news from an RSS feed
    private void buildNews() throws Exception {
                
        // get the feed from the URL
        SyndFeed feed = getRSSFeed(NEWS_FEED);
        DepartmentNews news;
        
        for(Iterator it = feed.getEntries().iterator(); it.hasNext();) {
            SyndEntry entry = (SyndEntry)it.next();
            if(entry != null && entry.getDescription() != null) {
                news = new DepartmentNews();
                news.setTitle(entry.getTitle().trim());
                news.setContents(Jsoup.parse(entry.getDescription().getValue()).text().trim());
                DateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy");
                Date date = entry.getPublishedDate();
                // set the publishing date based on the entry's <pubDate>
                news.setPubDate(dateFormat.format(date).toString());
                // add the news to the list
                newsList.add(news);
            }
        }        
    }
    
    // method for building the personnel list based on the department's web site
    private void buildPersonnel() throws Exception {
        // GET the document
        Document doc = Jsoup.connect(PERSONNEL_URL).get();
        // create list for various personnel information
        Elements lastNames, firstNames, titles, rooms, phones;
        
        // parse all the last names based on the class of the <td>
        lastNames = doc.getElementsByClass("views-field-field-db-sukunimi-value");
        // parse all the first names based on the class of the <td>
        firstNames = doc.getElementsByClass("views-field-field-db-kutsumanimi-value");
        // parse all the titles based on the class of the <td>
        titles = doc.getElementsByClass("views-field-field-db-titteli-value");
        // parse all the room numbers based on the class of the <td>
        rooms = doc.getElementsByClass("views-field-field-db-sali-nro-value");
        // parse all the phone numbers based on the class of the <td>
        phones = doc.getElementsByClass("views-field-field-db-puhelin-tyo-value");
        
        // iterate through the last names and combine them with corresponding information
        // NOTE: all the element lists contain the same number of items
        for(int i = 0; i < lastNames.size(); i++) {
            Person p = new Person();
            p.setFirstName(firstNames.get(i).text());
            p.setLastName(lastNames.get(i).text());
            p.setTitle(titles.get(i).text());
            p.setRoom(rooms.get(i).text());
            p.setPhone(phones.get(i).text());
            // add the person to the list
            personList.add(p);
        }
    }
    
    private void buildResearchGroups() throws Exception {
        Document doc = Jsoup.connect(RESEARCH_GROUPS_URL).get();
        Elements elements = doc.getElementsByTag("li");
        for(Element e : elements) {
            if(e.hasClass("views-row")) {
                ResearchGroup rg = new ResearchGroup();
                rg.setInvestigator(e.getElementsByClass("views-field-field-prinvestigator-value").text());
                rg.setName(e.getElementsByClass("views-field-title").text());
                researchGroups.add(rg);                
            }
        }
        
    }
    
    // method for build the research groups based on the department's web site
    private void buildLunchMenus() throws Exception {
        
        // get the feed from the URL
        SyndFeed feed = getRSSFeed(MENU_EXACTUM);
        // create the diner object for Exactum
        Diner diner = new Diner("Exactum");
        
        // add Exactum to the list of diners
        menuList.getDiners().add(diner);
        
        // iterate through the daily menus
        for(Iterator it = feed.getEntries().iterator(); it.hasNext();) {
            SyndEntry entry = (SyndEntry)it.next();
            if(entry != null && entry.getDescription() != null && !Jsoup.parse(entry.getDescription().getValue()).text().isEmpty()) {
                // create a new daily menu
                DailyMenu dailyMenu = new DailyMenu();
                dailyMenu.setDate(entry.getTitle());
                dailyMenu.setMenuContents(entry.getDescription().getValue());
                // add the menu to the diners menu list
                diner.getDailyMenuList().add(dailyMenu);
            }
        }
        
        feed = getRSSFeed(MENU_CHEMICUM);
        // create the diner object for Chemicum
        diner = new Diner("Chemicum");
        
        // add Chemicum to the list of diners
        menuList.getDiners().add(diner);

        for(Iterator it = feed.getEntries().iterator(); it.hasNext();) {
            SyndEntry entry = (SyndEntry)it.next();
            if(entry != null && entry.getDescription() != null && !Jsoup.parse(entry.getDescription().getValue()).text().isEmpty()) {
                DailyMenu dailyMenu = new DailyMenu();
                dailyMenu.setDate(entry.getTitle());
                dailyMenu.setMenuContents(entry.getDescription().getValue());
                diner.getDailyMenuList().add(dailyMenu);
            }
        }        
        
    }
    
    // method for build the links between entities / bubbles
    private void buildLinks() {
        // get the courses
        List<Course> basic = courseList.getBasic().getCourses();
        List<Course> intermediate = courseList.getIntermediate().getCourses();
        List<Course> advanced = courseList.getAdvanced().getCourses();
        List<Course> seminar = courseList.getSeminar().getCourses();
        List<Course> doctorate = courseList.getDoctorate().getCourses();
        List<Course> other = courseList.getOther().getCourses();
        
        // link all the diners to the Menus main bubble
        for(Diner d : menuList.getDiners()) {
            menuList.addLink(d.getId());
            // link all the daily menus to the corresponding diner
            for(DailyMenu dm : d.getDailyMenuList()) {
                // only add the menu if the daily menu is relevant
                if(!dm.isInPast()) {
                    d.addLink(dm.getId());
                }
            }
        }
        
        // link the price list bubble to the Menus bubble
        menuList.addLink(priceList.getId());        
        
        // link the subcategories of courses to the main course bubble
        courseList.addLink(courseList.getBasic().getId());
        courseList.addLink(courseList.getIntermediate().getId());
        courseList.addLink(courseList.getAdvanced().getId());
        courseList.addLink(courseList.getSeminar().getId());
        courseList.addLink(courseList.getDoctorate().getId());
        courseList.addLink(courseList.getOther().getId());
        
        // link all the basic courses to the basic course main bubble
        for(Course c : courseList.getBasic().getCourses()) {
            courseList.getBasic().addLink(c.getId());
        }
        // link all the intermediate courses to the intermediate course main bubble
        for(Course c : courseList.getIntermediate().getCourses()) {
            courseList.getIntermediate().addLink(c.getId());
        }
        // link all the advanced courses to the advanced courses main bubble
        for(Course c : courseList.getAdvanced().getCourses()) {
            courseList.getAdvanced().addLink(c.getId());
        }
        // link all the seminars to the seminars main bubble
        for(Course c : courseList.getSeminar().getCourses()) {
            courseList.getSeminar().addLink(c.getId());
        }
        // link all the doctorate courses to the doctorate courses main bubble
        for(Course c : courseList.getDoctorate().getCourses()) {
            courseList.getDoctorate().addLink(c.getId());
        }
        // link all the other courses to the other courses main bubble
        for(Course c : courseList.getOther().getCourses()) {
            courseList.getOther().addLink(c.getId());
        }        
        
        // link all the news to the News main bubble
        for(DepartmentNews dn : newsList.getNews()) {
            newsList.addLink(dn.getId());
        }
        
        // link all the events to the Events main bubble
        for(DepartmentEvent de : eventList.getEvents()) {
            eventList.addLink(de.getId());
        }
        
        // link all the floors to the Maps bubble
        for(Floor f : maps.getFloors()) {
            maps.addLink(f.getId());
        }
        
        // link all the research groups to the research groups main bubble
        for(ResearchGroup rg : researchGroups.getGroups()) {
            researchGroups.addLink(rg.getId());
        }
        
        // iterate through personnel
        for(Person p : personList.getPersons()) {

            // iterate through various types of courses
            for(Course c : basic) {
                // iterate through course events (exercise sessions  and lectures)
                for(Event e : c.getTapaht()) {
                    // if the person is a lecturer or an assisant on this course
                    if(p.getName().equals(e.getVetaja())) {
                        // and if the link does not yet exist
                        if(!c.linkExists(p.getId())) {
                            // link the person to the course owning this event
                            c.addLink(p.getId());
                        }
                        // Experimental for adding only relevant people!
                        // add the person to the personnel main bubble only if
                        // the person is linked to a course of a research group
                        if(!personList.linkExists(p.getId())) {
                            personList.addLink(p.getId());
                        }
                    }
                }
            }
            // same as for basic courses
            for(Course c : intermediate) {
                for(Event e : c.getTapaht()) {
                    if(p.getName().equals(e.getVetaja())) {
                        if(!c.linkExists(p.getId())) {
                            c.addLink(p.getId());
                        }
                        // Experimental for adding only relevant people!
                        if(!personList.linkExists(p.getId())) {
                            personList.addLink(p.getId());
                        }                        
                    }
                }
            }
            // same as for basic courses
            for(Course c : advanced) {
                for(Event e : c.getTapaht()) {
                    if(p.getName().equals(e.getVetaja())) {
                        if(!c.linkExists(p.getId())) {
                            c.addLink(p.getId());
                        }
                        // Experimental for adding only relevant people!
                        if(!personList.linkExists(p.getId())) {
                            personList.addLink(p.getId());
                        }                        
                    }
                }
            }
            // same as for basic courses
            for(Course c : seminar) {
                for(Event e : c.getTapaht()) {
                    if(p.getName().equals(e.getVetaja())) {
                        if(!c.linkExists(p.getId())) {
                            c.addLink(p.getId());
                        }
                        // Experimental for adding only relevant people!
                        if(!personList.linkExists(p.getId())) {
                            personList.addLink(p.getId());
                        }                        
                    }
                }
            }
            // same as for basic courses
            for(Course c : doctorate) {
                for(Event e : c.getTapaht()) {
                    if(p.getName().equals(e.getVetaja())) {
                        if(!c.linkExists(p.getId())) {
                            c.addLink(p.getId());
                        }
                        // Experimental for adding only relevant people!
                        if(!personList.linkExists(p.getId())) {
                            personList.addLink(p.getId());
                        }                        
                    }
                }
            }
            // same as for basic courses
            for(Course c : other) {
                for(Event e : c.getTapaht()) {
                    if(p.getName().equals(e.getVetaja())) {
                        if(!c.linkExists(p.getId())) {
                            c.addLink(p.getId());
                        }
                        // Experimental for adding only relevant people!
                        if(!personList.linkExists(p.getId())) {
                            personList.addLink(p.getId());
                        }                        
                    }
                }
            }
            
            // same as for basic courses
            for(ResearchGroup rg : researchGroups.getGroups()) {
                if(rg.getLastName() != null) {
                    if(rg.getLastName().equals(p.getLastName()) && p.getFirstName().startsWith(rg.getInitial())) {
                        if(!rg.linkExists(p.getId())) {
                            rg.addLink(p.getId());   
                        }
                        // Experimental for adding only relevant people!
                        if(!personList.linkExists(p.getId())) {
                            personList.addLink(p.getId());
                        }                        
                    }
                }
            }            
        }

    }
    
    // method for build the maps
    // NOTE: this method does not fetch information from the web
    private void buildMaps() {
        // create the floor objects and add them to the map list
        Floor first = new Floor("1st Floor");
        maps.add(first);
        Floor second = new Floor("2nd Floor");
        maps.add(second);
        Floor third = new Floor("3rd Floor");
        maps.add(third);
        Floor fourth = new Floor("4th Floor");
        maps.add(fourth);
        Floor base = new Floor("Basement Floor");
        maps.add(base);
    }
    
    // method for test output printing
    public void testOutput() {
        System.out.println("Printing output information: ");
        System.out.println("Courses bubble and number of links: " + courseList.getBubbleTitle() + " & " + courseList.getLinks().size());
        System.out.println("\t Basic studies and number of links: " + courseList.getBasic().getBubbleTitle() + " & " + courseList.getBasic().getLinks().size());
        System.out.println("\t Intermediate studies and number of links: " + courseList.getIntermediate().getBubbleTitle() + " & " + courseList.getIntermediate().getLinks().size());
        System.out.println("\t Advanced studies and number of links: " + courseList.getAdvanced().getBubbleTitle() + " & " + courseList.getAdvanced().getLinks().size());
        System.out.println("\t Seminar studies and number of links: " + courseList.getSeminar().getBubbleTitle() + " & " + courseList.getSeminar().getLinks().size());
        System.out.println("\t Doctorate studies and number of links: " + courseList.getDoctorate().getBubbleTitle() + " & " + courseList.getDoctorate().getLinks().size());
        System.out.println("\t Other studies and number of links: " + courseList.getOther().getBubbleTitle() + " & " + courseList.getOther().getLinks().size());
        System.out.println("Personnel bubble and number of links: " + personList.getBubbleTitle() + " & " + personList.getLinks().size());
        System.out.println("Maps bubble and number of links: " + maps.getBubbleTitle() + " & "+ maps.getLinks().size());
        System.out.println("Research Groups bubble and number of links: " + researchGroups.getBubbleTitle() + " & " + researchGroups.getLinks().size());
        System.out.println("News bubble and number of links: " + newsList.getBubbleTitle() + " & " + newsList.getLinks().size());
        System.out.println("Events bubble and number of links: " + eventList.getBubbleTitle() + " & " + eventList.getLinks().size());
        System.out.println("Lunch Menus bubble and number of links " + menuList.getBubbleTitle() + " & " + menuList.getLinks().size());
        System.out.println("\tExactum Lunch Menu bubble and number of links: " + menuList.getDiners().get(0).getBubbleTitle() + " & " + menuList.getDiners().get(0).getLinks().size());
        System.out.println("\tChemicum Lunch Menu bubble and number of links: " + menuList.getDiners().get(1).getBubbleTitle() + " & " + menuList.getDiners().get(1).getLinks().size());       
        System.out.println("\tPrice List bubble and number of links: " + priceList.getBubbleTitle() + " & " + priceList.getLinks().size());
    }
    
    // method for building everything with one call
    public void build() {
        try {
            // build bubbles
            runJSONParser();
            buildNews();
            buildEvents();
            buildLunchMenus();
            buildPersonnel();
            buildResearchGroups();
            buildMaps(); 
            buildPriceList();
            // create links between bubbles
            buildLinks();
            // write the bubbles to an XML file
            System.out.println("File printing complete: " + writeToFile());
            // print the test output
            testOutput();
        }
        catch(Exception e) {
        }        
    }
    
    // method for writing the bubble entities into an XML file
    public boolean writeToFile() {
        try {
            // open a new file called eit.xml and define encoding as UTF-8
            BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("eit.xml"), "UTF-8"));
            
            // write the static information
            out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            out.write("<data>\n");
            out.write("<add>\n");
            
            // write the menus main bubble
            out.write(menuList.toBubble());
            for(Diner diner : menuList.getDiners()) {
                // write the diner bubbles
                out.write(diner.toBubble());
                for(DailyMenu dailyMenu : diner.getDailyMenuList()) {
                    // write the daily menu bubbles
                    out.write(dailyMenu.toBubble());
                }
            }
            
            // write the courses main bubble
            out.write(courseList.toBubble());

            // write the basic courses subbubble
            out.write(courseList.getBasic().toBubble());
            for(Course c : courseList.getBasic().getCourses()) {
                // write all the basic course bubbles
                out.write(c.toBubble());
            }

            // same as with basic courses
            out.write(courseList.getIntermediate().toBubble());
            for(Course c : courseList.getIntermediate().getCourses()) {
                out.write(c.toBubble());
            }
            
            // same as with basic courses
            out.write(courseList.getAdvanced().toBubble());
            for(Course c : courseList.getAdvanced().getCourses()) {
                out.write(c.toBubble());
            }            
            
            // same as with basic courses
            out.write(courseList.getSeminar().toBubble());
            for(Course c : courseList.getSeminar().getCourses()) {
                out.write(c.toBubble());
            }            
            
            // same as with basic courses
            out.write(courseList.getDoctorate().toBubble());
            for(Course c : courseList.getDoctorate().getCourses()) {
                out.write(c.toBubble());
            }            
            
            // same as with basic courses
            out.write(courseList.getOther().toBubble());
            for(Course c : courseList.getOther().getCourses()) {
                out.write(c.toBubble());
            }
            
            // write the personnel main bubble
            out.write(personList.toBubble());
            for(Person p : personList.getPersons()) {
                // only write the person to the xml file if it is linked to something
                if(personList.linkExists(p.getId())) {
                    out.write(p.toBubble());
                }
            }
            
            // write the research groups main bubble
            out.write(researchGroups.toBubble());
            for(ResearchGroup rg : researchGroups.getGroups()) {
                // write research group bubbles
                out.write(rg.toBubble());
            }
            
            // write the maps main bubble
            out.write(maps.toBubble());
            for(Floor f : maps.getFloors()) {
                // write the floor bubbles
                out.write(f.toBubble());
            }
            
            // write the news main bubble
            out.write(newsList.toBubble());
            // write the events main bubble
            out.write(eventList.toBubble());
            for(DepartmentNews dn : newsList.getNews()) {
                // write the news bubbles
                out.write(dn.toBubble());
            }
            for(DepartmentEvent de : eventList.getEvents()) {
                // write the event bubbles
                out.write(de.toBubble());
            }
            
            // write the price list bubble
            out.write(priceList.toBubble());
            
            // write additional XML configuration
            // NOTE: STATIC
            out.write("</add>\n");
            out.write("<bubbleCountCalendar>1</bubbleCountCalendar>\n"
                    + "<bubbleCountMap>0</bubbleCountMap>\n"
                    + "<bubbleCountSponsor>1</bubbleCountSponsor>\n"
                    + "<bubbleCountBrowser>0</bubbleCountBrowser>\n"
                    + "<visibleUgcCount>20</visibleUgcCount>\n"
                    + "<maximumBubbleCount>800</maximumBubbleCount>\n"
                    + "<runMode>hiit-indoors</runMode>\n"
                    + "<enableGhost>false</enableGhost>\n"
                    + "<enableHighlightOfMagneticLink>true</enableHighlightOfMagneticLink>\n"
                    + "<enableMagnetics>true</enableMagnetics>\n"
                    + "<magneticForceAmplitude>50.0</magneticForceAmplitude>\n"
                    + "</data>");
           
            // close the writer
            out.close();
            // return true if the writing was successful
            return true;
        }
        catch(Exception e) {
            // return false if the writing failed
            return false;
        }                
    }
    
    // method for opening and fetching an RSS feed from an URL
    private SyndFeed getRSSFeed(String url) throws Exception {
        // create new URL
        URL feedSource = new URL(url);
        XmlReader xmlReader = null;
        
        try {
            // create a new XmlReader
            xmlReader = new XmlReader(feedSource);
            // build the feed from the URL
            SyndFeed feed = new SyndFeedInput().build(xmlReader);
            
            // return the feed
            return feed;
        }
        finally {
            // close the reader if it was null
            if(xmlReader != null) {
                xmlReader.close();
            }
        }
    }
    
    // method for fetching the price categories from Unicafe web page
    private void buildPriceList() throws IOException {
        // fetch the web page
        Document doc = Jsoup.connect(PRICE_LIST_URL).get();
        // parse all the table elements into a list
        Elements tables = doc.getElementsByTag("table");
        // get the first table (second one is the price list for additional portions)
        Element priceTable = tables.first();
        // get the rows of the table
        Elements tableRows = priceTable.getElementsByTag("tr");
        // remove the first row from the list (the row is a header row)
        tableRows.remove(0);
        
        // create a new PriceList object
        priceList = new PriceList();
        
        // iterate through the rows
        for(int i = 0; i < tableRows.size(); i++) {
            // parse the <td> elements into a list
            Elements rowColumns = tableRows.get(i).getElementsByTag("td");
            // remove the first element (header)
            rowColumns.remove(0);
            
            // new StringBuilder for concatenating the prices into a single string
            StringBuilder result = new StringBuilder();
            
            // iterate through the rest of the <td> elements
            for(int j = 0; j < rowColumns.size(); j++) {
                // fill out regular prices if j was zero
                if(j == 0) {
                    // fix trim() not working when i was zero by manually parsing a substring to preserver currency char
                    if(i == 0) {
                        result.append("Price: ").append(Escaper.escapeXml(rowColumns.get(j).text().substring(0, rowColumns.get(j).text().length()-3))).append(", ");
                        
                    }
                    // otherwise trim() will work out fine to remove excess whitespaces
                    else {
                        result.append("Price: ").append(rowColumns.get(j).text().trim()).append(", ");
                    }                    
                }
                // fill out student prices if j was 1
                else if(j == 1) {
                    result.append("Student: ").append(rowColumns.get(j).text().trim()).append(", ");
                }
                
                // fill out postgraduate prices if j was 2
                else if(j == 2) {
                    result.append("Postgraduate: ").append(rowColumns.get(j).text().trim()).append(", ");
                }
                // otherwise fill out HYY/HUU prices
                else {
                    result.append("HYY/HUU: ").append(rowColumns.get(j).text().trim());
                }
            }
            
            // set string to Maukkaasti if i was zero
            if(i == 0) {
                priceList.setMaukkaasti(result.toString());
            }
            // set string to Edullisesti if i was 1
            else if(i == 1) {
                priceList.setEdullisesti(result.toString());
            }
            // otherwise set string to Kevyesti
            else {
                priceList.setKevyesti(result.toString());
            }
                        
        }
    }
}
